package com.leochan.shuniu.database;

import java.util.ArrayList;
import java.util.List;

import com.leochan.shuniu.database.model.BaseModel;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

public abstract class BaseDao<T extends BaseModel> implements Dao<T> {

	private static final String DAO = "DAO";

	protected SQLiteDatabase db;

	SQLiteDatabase getDB() {
		return DatabaseManager.getInstance().getDatabase();
	}

	protected abstract String getTableName();

	protected String getKeyName() {
		return "_id";
	}

	protected abstract ContentValues model2ContentValues(T model);

	protected abstract T cursor2Model(Cursor cursor);

	public BaseDao(SQLiteDatabase db) {
		super();
		this.db = db;
	}

	public void createTable() {
		db.execSQL(createTableSql());
		initData();
	}

	protected abstract String createTableSql();

	/**
	 * override this method if you want to insert initial data when table is
	 * created.
	 */
	protected void initData() {

	}

	@Override
	public int countAll() {
		return countBy("1=1", null);
	}

	@Override
	public int countBy(String select, String[] args) {
		Cursor cursor = null;
		try {
			String countSQL = "SELECT COUNT(*) FROM " + getTableName()
					+ " WHERE " + select;
			cursor = getDB().rawQuery(countSQL, args);
			cursor.moveToFirst();
			int count = cursor.getInt(0);
			Log.d(DAO, "sql: " + countSQL + "; args: " + _argsToString(args)
					+ "; count[" + count + "]");
			return count;
		} finally {
			if (cursor != null)
				cursor.close();
		}
	}

	public int countBy(String select) {
		return countBy(select, null);
	}

	@Override
	public int delete(T model) {
		return deleteById(model.getId());
	}

	@Override
	public int deleteById(long id) {
		return deleteBy(getKeyName() + "=" + id, null);
	}

	@Override
	public int deleteBy(String select, String[] args) {
		synchronized (getWriteLock()) {
			try {
				int rows = getDB().delete(getTableName(), select, args);
				Log.d(DAO, "delete " + getTableName() + ": " + select
						+ "; args: " + _argsToString(args) + "; deleted rows["
						+ rows + "]");
				return rows;
			} catch (Exception e) {
				return 0;
			}
		}
	}

	public int deleteBy(String select) {
		return deleteBy(select, null);
	}

	@Override
	public ArrayList<T> queryAll() {
		return queryBy("1=1", null);
	}

	@Override
	public ArrayList<T> queryBy(String select, String[] args) {
		return queryBy(getDB(), select, args);
	}

	public ArrayList<T> queryBy(SQLiteDatabase db, String select, String[] args) {
		Cursor cursor = null;
		try {
			String sql = "SELECT * FROM " + getTableName() + " WHERE " + select;
			cursor = db.rawQuery(sql, args);
			if (!cursor.moveToFirst()) {
				Log.d(DAO, "sql: " + sql + "; rows[0]");
				return new ArrayList<T>();
			}
			ArrayList<T> list = new ArrayList<T>(cursor.getCount());
			do {
				list.add(cursor2Model(cursor));
			} while (cursor.moveToNext());
			Log.d(DAO, "sql: " + sql + "; args: " + _argsToString(args)
					+ "; rows[" + list.size() + "]");
			return list;
		} finally {
			if (cursor != null)
				cursor.close();
		}
	}

	@Override
	public ArrayList<T> queryByOrder(String select, String[] args,
			String orderCol, Order order) {
		Cursor cursor = null;
		try {
			String sql = "SELECT * FROM " + getTableName() + " WHERE " + select
					+ " ORDER BY " + orderCol + order.toString();
			cursor = getDB().rawQuery(sql, args);
			if (!cursor.moveToFirst()) {
				Log.d(DAO, "sql: " + sql + "; rows[0]");
				return new ArrayList<T>();
			}
			ArrayList<T> list = new ArrayList<T>(cursor.getCount());
			do {
				list.add(cursor2Model(cursor));
			} while (cursor.moveToNext());
			Log.d(DAO, "sql: " + sql + "; args: " + _argsToString(args)
					+ "; rows[" + list.size() + "]");
			return list;
		} finally {
			if (cursor != null)
				cursor.close();
		}
	}

	private String _argsToString(String[] args) {
		if (args == null || args.length == 0)
			return "{}";
		StringBuffer sb = new StringBuffer("{");
		for (String str : args) {
			sb.append(str).append(", ");
		}
		sb.append("}");
		return sb.toString();
	}

	public List<T> queryBy(String select) {
		return queryBy(select, null);
	}

	@Override
	public Pager<T> queryAllPage(int pageSize) {
		return queryByPage("1=1", pageSize);
	}

	@Override
	public Pager<T> queryByPage(String select, int pageSize) {
		return new Pager<T>(pageSize, this, select);
	}

	@Override
	public T querySingleBy(String select, String[] args) {
		List<T> list = queryBy(select, args);
		if (list.size() == 0)
			return null;
		return list.get(0);
	}

	public T querySingleBy(String select) {
		return querySingleBy(select, null);
	}

	@Override
	public T queryById(long id) {
		Cursor cursor = null;
		try {
			String sql = "SELECT * FROM " + getTableName() + " WHERE "
					+ getKeyName() + "=" + id;
			cursor = getDB().rawQuery(sql, null);
			if (!cursor.moveToFirst()) {
				Log.d(DAO, "sql: " + sql + "; rows[0]");
				return null;
			}
			Log.d(DAO, "sql: " + sql + "; rows[1]");
			return cursor2Model(cursor);
		} finally {
			if (cursor != null)
				cursor.close();
		}
	}

	@Override
	public Cursor cursorAll() {
		return cursorBy("1=1", null);
	}

	@Override
	public Cursor cursorBy(String select, String[] args) {
		String sql = "SELECT * FROM " + getTableName() + " WHERE " + select;
		return getDB().rawQuery(sql, args);
	}

	public Cursor cursorRecentRead(String orderby) {
		String sql = "SELECT * FROM (SELECT * FROM "
				+ getTableName()
				+ " WHERE lastAccessTime>0 order by lastAccessTime DESC LIMIT 20 OFFSET 0) "
				+ orderby;
		return getDB().rawQuery(sql, null);
	}

	protected abstract Object getWriteLock();

	@Override
	public T saveOrUpdate(T model) {
		synchronized (getWriteLock()) {
			try {
				if (model.getId() > 0) {
					return update(model);
				} else {
					return save(model);
				}
			} catch (Exception e) {
				return model;
			}
		}
	}

	public T saveOrUpdate(SQLiteDatabase db, T model) {
		synchronized (getWriteLock()) {
			try {
				if (model.getId() > 0)
					return update(db, model);
				else
					return save(db, model);
			} catch (Exception e) {
				return model;
			}
		}
	}

	private T save(T model) {
		return save(getDB(), model);
	}

	private T save(SQLiteDatabase db, T model) {
		ContentValues values = model2ContentValues(model);
		long id = db.insert(getTableName(), null, values);
		model.setId(id);
		Log.d(DAO, "insert " + getTableName() + "; id[" + id + "]");
		return model;
	}

	private T update(T model) {
		return update(getDB(), model);
	}

	private T update(SQLiteDatabase db, T model) {
		ContentValues values = model2ContentValues(model);
		int rows = db.update(getTableName(), values,
				getKeyName() + "=" + model.getId(), null);
		Log.d(DAO, "update " + getTableName() + "; id[" + model.getId()
				+ "]; updated rows[" + rows + "]");
		return model;
	}

}
